import fs from 'fs-extra'
import path from 'path'
import pixelmatch from 'pixelmatch'
import { PNG } from 'pngjs'
import randomstring from 'randomstring'
import { Layer, readPsd } from 'ag-psd'
import 'ag-psd/dist/initializeCanvas.js'
import env from './Env.js'
import { ILayerWithCanvas, PrefabGenerator } from './PrefabGenerator.js'
import { Canvas } from 'canvas'

interface ICanvasInfo {
    buffer: Buffer
    png: PNG
    file: string
}

export class Convertor {
    private readonly nameUsageMap: { [name: string]: number } = {}

    private readonly canvasFileMap: { [canvas: string]: string } = {}
    private readonly canvasMap: { [size: string]: ICanvasInfo[] } = {}

    private readonly prefabGen = new PrefabGenerator()

    public async make (): Promise<void> {
        const buffer = await fs.readFile(env.opts.input)

        // read only document structure
        // const psd1 = readPsd(buffer, { skipCompositeImageData: true, skipThumbnail: true, skipLinkedFilesData: true })
        const psd = readPsd(buffer, { skipCompositeImageData: true, skipThumbnail: true, skipLinkedFilesData: true })
        // by defaults `canvas` field type is HTMLCanvasElement, so it needs to be cast to `any`
        // or node-canvas `Canvas` type, in order to call `toBuffer` method
        if (psd.children !== undefined) {
            for (const child of psd.children) {
                if (child.hidden === true) continue
                await this.saveCanvas(child)
            }
        }
        await this.prefabGen.make(psd)
    }

    private async saveCanvas (layer: Layer): Promise<void> {
        if (layer.canvas !== undefined &&
            (layer.text === undefined || this.prefabGen.isArtFont(layer))) {
            const canvas = layer.canvas as unknown as Canvas
            const canvasURL = canvas.toDataURL()
            let pngFile = this.canvasFileMap[canvasURL]
            if (pngFile === undefined) {
                // compare buffers
                const buffer = canvas.toBuffer()
                const png = PNG.sync.read(buffer)
                const size = `${canvas.width}x${canvas.height}`
                let infos = this.canvasMap[size]
                if (infos !== undefined) {
                    for (const info of infos) {
                        const diff = pixelmatch(png.data, info.png.data, null, png.width, png.height, { threshold: 0.1 })
                        if (diff === 0) {
                            console.log(`${layer.name ?? '??'} is exactly the same as ${info.file} by PIXELMATCH`)
                            pngFile = info.file
                            break
                        }
                    }
                } else {
                    this.canvasMap[size] = infos = []
                }

                if (pngFile === undefined) {
                    let pngName = layer.name
                    if (pngName === undefined) {
                        pngName = 'noname'
                    } else {
                        // remove slashes
                        pngName = pngName.replace(/\s+/g, '').replace(/\//g, '').replace(/\\/g, '')
                    }
                    // if (pngName === '多边形1') {
                    //     console.log(canvasURL)
                    // }
                    if (pngName in this.nameUsageMap) {
                        pngName += '_' + randomstring.generate(6)
                    }
                    this.nameUsageMap[pngName] = 1
                    pngFile = path.join(env.cfg.PNG_ROOT.replace(/\$name\$/g, env.opts.name), pngName + '.png')
                    this.canvasFileMap[canvasURL] = pngFile
                    infos.push({ buffer, png, file: pngFile })

                    console.log('saving png:', pngFile)
                    const pngPath = path.join(env.opts.project, pngFile)
                    await fs.ensureDir(path.dirname(pngPath))
                    await fs.writeFileSync(pngPath, buffer)
                }
            } else {
                console.log(`${layer.name ?? '??'} is exactly the same as ${pngFile} by URL`)
            }
            const layerAlias = layer as ILayerWithCanvas
            layerAlias.pngFile = pngFile
        }

        if (layer.children !== undefined) {
            for (const child of layer.children) {
                if (child.hidden === true) continue
                await this.saveCanvas(child)
            }
        }
    }
}
